iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0
自我挑戰組

重新複習JavaScript系列 第 18

[Day -18] 事件機制的原理

  • 分享至 

  • xImage
  •  

JavaScript 是一個事件驅動 (Event-driven) 的程式語言,當瀏覽器載入網頁開始讀取後,雖然馬上會讀取 JavaScript 事件相關的程式碼,但是必須等到「事件」被觸發(如使用者點擊、按下鍵盤等)後,才會再進行對應程式的執行。

Event Flow(事件流程)

Imgur

事件流程可以分成兩種機制:

  • Event Bubbling(事件冒泡)
  • Event Capturing(事件捕獲)

Event Bubbling(事件冒泡)

事件冒泡指的是「從啟動事件的節點 (event.target) 開始,逐層往上傳遞」,直到整個網頁的根節點 document為止。如下圖:

Imgur

因此最上層的元素幾乎可以知道內層元素所有發生的事情,其中包含幾個重要的屬性:

  • event.target:觸發此事件的元素可以透過 event.target 來取得,這個元素在整個冒泡過程中不會改變。
  • event.currentTarget:綁定此事件的元素,通常和 this 指的是同一個元素。
  • this:指的是處理此事件的元素,和 event.currentTarget 指稱同一個元素。

如果到某一層你想阻擋事件繼續往下一層傳播,你可以在該層用 event.stopPropagation()來阻止事件的傳播。但要記得除非有需要,否則不需要停止冒泡事件。

Event Capturing(事件捕獲)

跟Event Bubbling 相反,由上而下的傳遞則稱為「事件捕獲」。Capturing 機制很少在實作中被運用,因此他通常是隱藏起來的。下圖為事件捕獲的順序:

Imgur

由於元素之間有這樣的上下回報機制,我們的事件才能順利地動起來。

但在實作上,幾乎不會遇到情境需要運用 Capturing,但如果刻意想打開時,可以運用element.addEventListener的第三個參數useCapture 從原本預設的false 改成true

element.addEventListener("click", handler, useCapture)

EventTarget.addEventListener()事件監聽

剛剛我們有提到element.addEventListener 那這個是什麼呢

我們現在設想一個情境,我要在網頁上設置一個按鈕,當使用者點擊這個按鈕時會印出Hello。

我要用什麼方法讓JavaScript知道我已經點擊按鈕,你必須開始工作了呢?

這時候我們就需要「事件監聽器 (event listener)」,來扮演 HTML 和 JavaScript 之間的接線生。

Imgur

event handler(事件處理器 )是在特定事件發生時執行的程式碼,用於回應使用者或瀏覽器的操作。事件處理程序通常用於為使用者界面元素(例如按鈕、連結、輸入欄等)添加互動性和功能。

了解了什麼是 event listener 後,我們來看一下語法:

element.addEventListener(type, function, useCapture)

type:這會是一個string,用來指定事件類型,下面會有介紹。

function:當事件被監聽到時要運行的函式,可以直接使用匿名函式。

useCapture:一個布林值,用於指定事件處理程序是在Capturing時(選擇true)還是在Bubbling時(選擇false)被執行。大多數情況下,可以省略這個參數,默認值是 false

在都了解後,我們把剛剛設想的情境完成吧:

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log('Hello');
});

Event Type

事件的種類實在太多,以下就列出幾個常見的事件,剩下的就附上MDN網址:

滑鼠事件

鼠標在網頁上滑動時會跨越不同的元素邊界,它的事件設置有豐富的可能性,你可以點擊這裡查看完整的清單,但一般而言,最常見的有以下三種:

  • click - 鼠標點擊元素
  • mousemove - 鼠標滑過元素
  • mouseout - 鼠標離開元素

鍵盤事件

最常用常見的鍵盤事件有:

  • keydown - 點擊且長按一個鍵時
  • keyup - 放開按鍵時

完整的清單可看這裡

表單事件

表單是網頁上獲取使用者回饋的重要元件,當你在網站上使用表單時,你的行為會受到 DOM 物件FormElementInterface 監控。關於表單需要另開一個主題討論,常見事件有:

  • submit - 提交表單時
  • focus - 點擊某個輸入框時
  • input - 輸入框內容改變時

document 事件

  • DOMContentLoaded - 當 HTML 下載完成並完整的建立 DOM 模型時觸發

就和我們總是在 </body> 前引入 JavaScript 檔案一樣,如果 HTML 還沒下載完就先進行 DOM 操作,勢必會遇到奇怪問題。在實務上除了注意檔案引入位置,在 JavaScript 時還會再包一層 DOMContentLoaded,確保萬無一失。

其他事件

Event — Web APIs | MDN

Event Delegation(事件指派)

Event Delegation 是一種在父元素上監聽事件,以便處理子元素事件的技術。

這種方法通常用於在有大量相似子元素的情況下,可以減少事件處理程序的數量,提高效率和性能。

基本思想是,將事件處理程序綁定到父元素,然後利用事件冒泡的特性,當子元素觸發該事件時,事件會一直冒泡到父元素。父元素可以根據觸發事件的子元素來決定如何處理。

下方的code是擷取之前練習todo list的部分片段的code:

// html:
	<div id="todo-title">
    <h4>Todo</h4>
    <ul id="my-todo" class="list-unstyled">
      <!-- display todos here -->
    </ul>
    <h4>Done</h4>
    <ul class="done-list list-unstyled"></ul>
  </div>
// Delete and check
const todoTitle = document.getElementById('todo-title')
todoTitle.addEventListener("click", function (event) {
  const target = event.target;
  const parentElement = target.parentElement;
  if (target.classList.contains("delete")) {
    ...
  }
});

我們看到我把event listner 放在最外層的,這樣的好處是我們不用掛載很多event listener。

如果我沒有使用這總方法的話,當今天我有100條todo時,我是不是需要掛100個event listener,光想到就覺得累><”

這段code還有一個地方是我們不認識的,那就是const target = event.target ,這段是什麼意思呢?

Event Object:

剛剛那行其實是我們用 event object裡的屬性**target** ,把最初觸發事件的 DOM 物件賦值在target這個變數裡。

那這個event object是怎麼來的?

當event listener 所監聽的事件發生時,瀏覽器會去執行我們在 addEventListener()  所指定的 function,也就是Event Handler 。

這個時候,EventListener 會去建立一個「事件物件」 (Event Object),裡面包含了所有與這個事件有關的屬性,並且以「參數」的形式傳給我們的 Event Handler。

其中有三個屬性要特別注意:

target(目標)

用途:需要知道到底我們觸發哪一個 DOM 元素時可以使用

const target = event.target

💡 this 跟 event.target 有點像,但是他們之間的區別是: 隨著 js 冒泡事件的發生,this 是會變化的,但 event.target 不會變化,它永遠是指觸發事件的 DOM 物件

preventDefault

用途:取消它們的預設行為,但並不會阻止事件向上傳遞 (事件冒泡) 。

有使用過的2個時機:

  • 按鈕用a標籤,因為a標籤必須要寫上連結,但點擊就是跳轉頁面。
  • form的submit就會自動傳送資料。
<form>
	<button type="submit">送出</button>
</form>

let btn = document.querySelector('button');
btn.addEventListener('click',e =>{
    e.preventDefault();
})//點按鈕時,表單不會被提交出去,頁面也不會重刷

stopPropagation()

用途:阻擋事件向上冒泡傳遞

在前半段我們有提到:

如果到某一層你想阻擋事件繼續往下一層傳播,你可以在該層用 event.stopPropagation()來阻止事件的傳播。

現在我們來看看實際狀況是怎樣吧:

// html
<div id="parent">
  <button id="child">點我</button>
</div>

// js
document.getElementById("child").addEventListener("click", function(event) {
  alert("子元素的點擊事件觸發了");
  event.stopPropagation(); // 阻止事件向上傳播
});

document.getElementById("parent").addEventListener("click", function() {
  alert("父元素的點擊事件觸發了");
});

在這個例子中,當點擊子元素時,子元素的點擊事件處理程序會觸發,並顯示一個警示框。同時,父元素的點擊事件處理程序也會觸發,並顯示另一個警示框。但是,由於在子元素的事件處理程序中使用了 stopPropagation(),父元素的事件不會繼續傳播,因此父元素的點擊事件處理程序不會被觸發。


本篇內容是從我之前的學習筆記整理出來的,說真的我搞不懂為什麼我以前的筆記居然有寫到Event inheritance這個東西,不要說當時的我應該根本還不知道什麼是繼承,就算現在你問我我也說得很不完整,我在想應該是網路上的文章直接照抄卻沒有去理解。
繼承的學習我把他安排在最後面,因為我還是有點搞不懂繼承還有原型><”


上一篇
[Day -17] 遍歷周邊 DOM 節點
下一篇
[Day -19] IIFE
系列文
重新複習JavaScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言